package com.franklin.ideaplugin.easytesting.spring.dubbo;

import com.franklin.ideaplugin.easytesting.common.log.ILogger;
import com.franklin.ideaplugin.easytesting.common.log.LoggerFactory;
import com.franklin.ideaplugin.easytesting.spring.dubbo.constants.DubboConstants;
import lombok.RequiredArgsConstructor;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.*;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentMap;

/**
 * 将dubbo的reference bean注入容器中（只处理字段，方法无法处理）
 *
 * @author Ye Junhui
 * @since 2023/7/13
 */
public class EasyTestingReferenceAnnotationBeanRegistrar
        implements BeanClassLoaderAware,
        BeanDefinitionRegistryPostProcessor, ApplicationListener<ApplicationReadyEvent> {

    private final static ILogger log = LoggerFactory.getLogger(EasyTestingReferenceAnnotationBeanRegistrar.class);

    private ClassLoader classLoader;
    private BeanDefinitionRegistry beanDefinitionRegistry;

    /**
     * 注解类型
     */
    private Class<? extends Annotation> referenceAnnType;

    /**
     * 缓存reference-bean的beanPostProcessor
     */
    private Class<?> referenceAnnotationBeanPostProcessorClazz;

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
        this.referenceAnnType = this.loadAnnClass();
        this.referenceAnnotationBeanPostProcessorClazz = this.loadReferenceAnnotationBeanPostProcessorClass();
    }

    /**
     * 加载注解类
     * @return
     */
    @Nullable
    private Class<? extends Annotation> loadAnnClass(){
        try {
            return (Class<? extends Annotation>) Class.forName(DubboConstants.ALIBABA_DUBBO_ANN_TYPE,true,this.classLoader);
        } catch (ClassNotFoundException e) { }
        try {
            return (Class<? extends Annotation>) Class.forName(DubboConstants.APACHE_DUBBO_ANN_TYPE,true,this.classLoader);
        } catch (ClassNotFoundException e) { }
        return null;
    }

    /**
     * 加载dubbo-bean后置处理器类
     * @return
     */
    @Nullable
    private Class<?> loadReferenceAnnotationBeanPostProcessorClass(){
        try {
            return Class.forName(DubboConstants.ALIBABA_DUBBO_BEAN_POST_PROCESSOR_TYPE,true,this.classLoader);
        } catch (ClassNotFoundException e) { }
        try {
            return Class.forName(DubboConstants.APACHE_DUBBO_BEAN_POST_PROCESSOR_TYPE,true,this.classLoader);
        } catch (ClassNotFoundException e) { }
        return null;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        this.beanDefinitionRegistry = registry;
    }

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        if (Objects.isNull(this.referenceAnnotationBeanPostProcessorClazz)){
            return;
        }
        log.info("dubbo {} loaded",DubboConstants.BEAN_POST_PROCESSOR__BEAN_NAME);
        try {
            ConfigurableApplicationContext applicationContext = event.getApplicationContext();
            Object referenceAnnotationBeanPostProcessor = applicationContext.getBean(
                    DubboConstants.BEAN_POST_PROCESSOR__BEAN_NAME,
                    this.referenceAnnotationBeanPostProcessorClazz
            );

            //获取referenceBean缓存
            Field referenceBeansCacheField = this.referenceAnnotationBeanPostProcessorClazz.getDeclaredField("referenceBeansCache");
            ReflectionUtils.makeAccessible(referenceBeansCacheField);
            ConcurrentMap<String, FactoryBean<?>> referenceBeansCache = (ConcurrentMap<String, FactoryBean<?>>) referenceBeansCacheField.get(referenceAnnotationBeanPostProcessor);

            //将referenceBean注入Spring容器
            referenceBeansCache.values().forEach(factoryBean -> {
                try {
                    Object referenceBean = factoryBean.getObject();
                    Class providerClazz = factoryBean.getObjectType();
                    //注册到BeanRegistry中
                    BeanDefinitionBuilder definition = BeanDefinitionBuilder
                            .genericBeanDefinition(providerClazz, () -> referenceBean);

                    definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
                    definition.setLazyInit(false);

                    log.info("Easy-Testing >>>> register dubbo reference bean,class : {} ,into Spring applicationContext",providerClazz);

                    AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
                    String proxyName = providerClazz.getCanonicalName() + "$EasyTestingDubboReferenceBean";
                    BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, proxyName, null);
                    BeanDefinitionReaderUtils.registerBeanDefinition(holder, beanDefinitionRegistry);
                } catch (Exception e) {
                    //ignore
                }
            });

        } catch (Exception e) {
            log.error("register dubbo-referenceBean into Spring applicationContext fail");
        }
    }

}
